﻿using System;
using System.Linq;
using GC_RF.BusinessEntities.DTOs;
using GC_RF.DataAccess.Abstractions.Interfaces.Factories;
using GC_RF.DataAccess.Abstractions.Interfaces.Helpers;
using GC_RF.DataAccess.Abstractions.Interfaces.Repositories;
using GC_RF.DataAccess.Models;
using GC_RF.Domain.Abstractions.Interfaces.Helpers;
using GC_RF.Domain.Abstractions.Interfaces.Services;
using GC_RF.Domain.Abstractions.Services;
using GC_RF.Domain.Constants;
using GC_RF.Domain.Enums;

namespace GC_RF.Domain.Services
{
	public class UserService : BaseService, IUserService
	{
		#region Variables

		private readonly IUserRepository _userRepository;
		private readonly IResetPasswordTokensRepository _resetPassTokensRepository;
		private readonly ICryptographicAdapter _cryptographicAdapter;

		#endregion

		#region Consructors

		public UserService(IUnitOfWork unitOfWork, IRepositoryFactory repositoryFactory, ICryptographicAdapter cryptographicAdapter)
			: base(unitOfWork, repositoryFactory)
		{
			_userRepository = RepositoryFactory.CreateUserRepository();
			if (_userRepository == null)
				throw new ArgumentNullException(string.Format("userRepository"));

			_resetPassTokensRepository = RepositoryFactory.CreateResetPasswordTokensRepository();
			if (_resetPassTokensRepository == null)
				throw new ArgumentNullException(string.Format("resetPassTokensRepository"));

			if (cryptographicAdapter == null)
				throw new ArgumentNullException(string.Format("cryptographicAdapter"));
			_cryptographicAdapter = cryptographicAdapter;
		}

		#endregion

		#region IUserService Members

		public bool IsUserNameExists(string data)
		{
			return _userRepository.Query(u => u.Name.ToLower().Trim().Equals((data ?? string.Empty).ToLower().Trim())).Any();
		}

		public bool IsEmailExists(string data)
		{
			return _userRepository.Query(u => u.Email.ToLower().Trim().Equals((data ?? string.Empty).ToLower().Trim())).Any();
		}

		public void Create(UserData data)
		{
			if (data == null) return;

			var user = new User
			{
				Email = data.Email,
				Name = data.Name,
				Password = data.Password,
				PasswordSalt = data.PasswordSalt,
				RoleId = data.RoleId
			};
			_userRepository.Add(user);

			UnitOfWork.SaveChanges();
		}

		public UserData GetUserByName(string data)
		{
			return _userRepository
				.Query(u => u.Name == data)
				.Select(u => new UserData
				{
					Email = u.Name,
					Id = u.Id,
					Name = u.Name,
					Password = u.Password,
					PasswordSalt = u.PasswordSalt,
					RoleId = u.RoleId
				})
				.FirstOrDefault();
		}

		public UserData GetUserById(int id)
		{
			return _userRepository
				.Query(u => u.Id == id)
				.Select(u => new UserData
				{
					Email = u.Name,
					Id = u.Id,
					Name = u.Name,
					Password = u.Password,
					PasswordSalt = u.PasswordSalt,
					RoleId = u.RoleId
				})
				.FirstOrDefault();
		}

		public UserData GetNewUser(string name, string email, string pass)
		{
			var user = new UserData
			{
				Name = (name ?? string.Empty).Trim(),
				Email = (email ?? string.Empty).Trim(),
				PasswordSalt = _cryptographicAdapter.CreateSalt(),
				RoleId = (int)UserRole.Manager
			};
			user.Password = _cryptographicAdapter.CreatePasswordHash(pass, user.PasswordSalt);

			return user;
		}

		public bool IsValidPassword(string password, string salt, string hash)
		{
			var currentHash = _cryptographicAdapter.CreatePasswordHash(password, salt);
			return (hash ?? string.Empty).Equals(currentHash ?? string.Empty, StringComparison.Ordinal);
		}

		public bool ChangePassword(int userId, string currentPass, string newPass)
		{
			if (string.IsNullOrWhiteSpace(currentPass) || string.IsNullOrWhiteSpace(newPass))
				return false;

			var user = _userRepository.Query(u => u.Id == userId).SingleOrDefault();
			if (user == null) return false;

			if (!IsValidPassword(currentPass, user.PasswordSalt, user.Password))
				return false;

			SetUserPassword(user.Id, newPass);
			return true;
		}

		public void SetUserPassword(int userId, string password)
		{
			if (string.IsNullOrWhiteSpace(password)) return;

			var user = _userRepository.Query(u => u.Id == userId).SingleOrDefault();
			if (user == null) return;

			user.Password = _cryptographicAdapter.CreatePasswordHash(password, user.PasswordSalt);

			UnitOfWork.SaveChanges();
		}

		public string GetResetPasswordToken(string email, int resetPaswordTokenLifitimeMinutes)
		{
			if (string.IsNullOrWhiteSpace(email)) return string.Empty;

			var user = _userRepository.Query(o => o.Email == email).SingleOrDefault();
			if (user == null) return string.Empty;

			var token = _cryptographicAdapter.GenerateEncodedUniqueToken();

			var resetToken = _resetPassTokensRepository.Query(o => o.User.Id == user.Id).SingleOrDefault();
			if (resetToken == null)
				_resetPassTokensRepository.Add(new ResetPasswordToken
				{
					Date = DateTime.Now,
					Token = token,
					User = user,
					TokenLifetime = resetPaswordTokenLifitimeMinutes > 0
						? resetPaswordTokenLifitimeMinutes
						: DefaultValues.DefaultResetPaswordTokenLifitimeMinutes
				});
			else
			{
				resetToken.Token = token;
				resetToken.Date = DateTime.Now;
				resetToken.TokenLifetime = resetPaswordTokenLifitimeMinutes > 0
					? resetPaswordTokenLifitimeMinutes
					: DefaultValues.DefaultResetPaswordTokenLifitimeMinutes;
			}

			UnitOfWork.SaveChanges();

			return token;
		}

		public bool VerifyResetPasswordToken(string resetToken)
		{
			if (string.IsNullOrWhiteSpace(resetToken)) return false;

			var resetPasswordToken = _resetPassTokensRepository.Query(o => o.Token == resetToken).SingleOrDefault();
			return resetPasswordToken != null && ValidateResetPasswordToken(resetPasswordToken);
		}

		public void ResetPassword(string resetToken, string newPassword)
		{
			if (string.IsNullOrWhiteSpace(resetToken) || string.IsNullOrWhiteSpace(newPassword))
				return;
			
			var token = _resetPassTokensRepository.Query(o => o.Token == resetToken).SingleOrDefault();
			if (token == null || !ValidateResetPasswordToken(token)) return;

			SetUserPassword(token.User.Id, newPassword);

			DeleteResetPasswordToken(token);
		}

		#endregion

		#region Internal Implementation

		private bool ValidateResetPasswordToken(ResetPasswordToken resetToken)
		{
			if (resetToken == null) return false;

			if (resetToken.Date.AddMinutes(resetToken.TokenLifetime) >= DateTime.Now) return true;

			DeleteResetPasswordToken(resetToken);
			return false;
		}

		private void DeleteResetPasswordToken(ResetPasswordToken resetToken)
		{
			if (resetToken == null) return;
			_resetPassTokensRepository.Delete(resetToken);
			UnitOfWork.SaveChanges();
		}

		#endregion
	}
}
